package lib

import (
	"errors"
	"fmt"
	"time"

	"gitee.com/krio/grpc-common/conf"
	"gitee.com/krio/helper/logger"
	"github.com/garyburd/redigo/redis"
)

var redisPool *redis.Pool

func InitRedis() *redis.Pool {
	cRedis := conf.C.Redis
	return InitRedisPool(cRedis.Dsn, cRedis.Password, cRedis.MaxIdle, cRedis.DB)
}

// redis 连接池
func InitRedisPool(dsn string, password string, maxIdle, db int) *redis.Pool {
	redisPool = &redis.Pool{
		MaxIdle: maxIdle, // 空闲数
		Dial: func() (redis.Conn, error) {
			c, err := redis.Dial("tcp", dsn, redis.DialPassword(password))
			if err != nil {
				return nil, err
			}
			if _, err = c.Do("SELECT", db); err != nil {
				return nil, err
			}
			return c, err
		},
		TestOnBorrow: func(c redis.Conn, t time.Time) error {
			if time.Since(t) > time.Minute {
				_, err := c.Do("PING")
				return err
			}
			return nil
		},
	}
	conn := redisPool.Get()
	defer conn.Close()

	if conn.Err() != nil {
		logger.Panicf("redis pool failed, err: %v", conn.Err())
	}

	if _, err := conn.Do("PING"); err != nil {
		logger.Panicf("redis ping failed, err: %v", err)
	}

	return redisPool
}

func GetRedisPool() *redis.Pool {
	return redisPool
}

// 加锁 (单机版的redis简单互斥锁，注意过期时间和程序运行时间)
// 分布式锁相关介绍：http://kaito-kidd.com/2021/06/08/is-redis-distributed-lock-really-safe
func RLock(key, operID string, expire int64) bool {
	r := redisPool.Get()
	defer r.Close()

	reply, err := redis.String(r.Do("SET", key, operID, "NX", "EX", expire))
	if err != nil {
		return false
	}

	return reply == "OK"
}

// 解锁
func UnRLock(key, operID string) bool {
	r := redisPool.Get()
	defer r.Close()

	reply, err := redis.String(r.Do("GET", key))
	if errors.Is(err, redis.ErrNil) { // 无 => true
		return true
	}
	if err != nil {
		return false
	}
	if reply == operID {
		_, _ = redis.String(r.Do("DEL", key)) // nolint 不关心失败
		return true
	}

	return false
}

// 基于redis的bitmap简单过滤器
// 问题：并发下+函数执行时间较长没啥大用
func RFilter(key string, id int32, f func() error) error {
	var (
		exi bool
		err error
	)
	if exi, err = GETBIT(key, id); err != nil {
		return err
	}
	if exi {
		return fmt.Errorf("already exists, key:%s id:%d", key, id)
	}
	if err = f(); err != nil {
		return err
	}
	if ok := SETBIT(key, id, true); !ok {
		return fmt.Errorf("setbit filed, key:%s id:%d", key, id)
	}

	return nil
}

// GETBIT key id
func GETBIT(key string, id int32) (bool, error) {
	r := redisPool.Get()
	defer r.Close()

	return redis.Bool(r.Do("GETBIT", key, id))
}

// SETBIT  key id 0/1
func SETBIT(key string, id int32, value bool) bool {
	r := redisPool.Get()
	defer r.Close()

	_, err := r.Do("SETBIT", key, id, value)
	return err == nil
}
